#!/usr/bin/env python3
# -*- coding:utf-8 -*-  
""" 
@author:Kam
@file: word_op.py 
@time: 2020/12/01
@contact: it.camel@gmail.com
@desc:  

# code is far away from bugs with the god animal protecting
    I love animals. They taste delicious.
              ┏┓      ┏┓
            ┏┛┻━━━┛┻┓
            ┃      ☃      ┃
            ┃  ┳┛  ┗┳  ┃
            ┃      ┻      ┃
            ┗━┓      ┏━┛
                ┃      ┗━━━┓
                ┃  神兽保佑    ┣┓
                ┃　永无BUG！   ┏┛
                ┗┓┓┏━┳┓┏┛
                  ┃┫┫  ┃┫┫
                  ┗┻┛  ┗┻┛ 
"""
import re
import docx
import os.path
import traceback
import collections
from enum import Enum

Content = collections.namedtuple('Content', ['type', 'value'])


class WAStatus(Enum):
    """
    Word analyse status
    """
    INIT = 0
    QUESTION_TYPE_ANALYSE = 1
    QUESTION_ANALYSE = 2
    ANSWER_ITEM_ANALYSE = 3
    SUB_QUESTION_ANALYSE = 4


class QAStatus(Enum):
    """ 
     Question Analyse Status
    """
    BACKGROUND_DESCRIPTION_ANALYSE = 0
    QUESTION_ANALYSE = 1
    ANSWER_ITEM_ANALYSE = 2

class QuestionType(Enum):
    GAP_FILLING = 0
    CHOICE_QUESTION = 1
    MULTI_CHOICE_QUESTION = 2
    FREE_RESPONSE_QUESTION = 3


class Question(object):
    def __init__(self):
        self.type = None
        self.index = None
        # list
        self.common_description = list()
        self.sub_questions = list()
        self.answer_items = list()

    def __repr__(self):
        msg = """
        index: %s
        type: %s
        common_description: %s
        sub_questions: %s
        answer_items: %s
        =====================================================
        """ % (str(self.type), str(self.index), str(self.common_description), str(self.sub_questions), str(self.answer_items))
        return msg




class WorkOperation(object):
    """ 
     
    """
    
    def __init__(self, word_path):
        """
        Constructor for WorkOperation
        :param word_path
        """
        self._word_path = word_path
        self._file_obj = docx.Document(word_path)
        self.content_list = list()
        self.dir_path = os.path.dirname(self._word_path)

    def doc2docx(self):
        """
        Convert doc file type to docx
        :return:
        """
        pass

    def get_content(self):
        # item: type list and item of the list is a object of Content defined out of the function
        file_name = os.path.basename(self._word_path)
        img_dir_path = os.path.join(self.dir_path, file_name + '_pictures')
        if not os.path.isdir(img_dir_path):
            os.makedirs(img_dir_path)
        pattern_image_id = re.compile('rId\d+')
        # < w:instrText xml:space = "preserve" > EQ \R(, 3) < / w: instrText >
        pattern_instr_text = re.compile(r'<w:instrText(.*)>(.*)</w:instrText>')
        for graph in self._file_obj.paragraphs:
            graph_content = list()
            for run in graph.runs:
                if run.text != '':
                    graph_content.append(Content('text', run.text))
                    continue
                else:
                    try:
                        # filter insert text
                        insert_text = pattern_instr_text.search(run.element.xml).group(2)
                        graph_content.append(Content('instr', insert_text))
                    except BaseException:
                        pass
                    try:
                        content_id = pattern_image_id.search(run.element.xml).group(0)
                        part = self._file_obj.part.related_parts[content_id]
                        if not part.content_type.startswith('image'):
                            continue
                        img_name = os.path.basename(part.partname)
                        image_path = os.path.join(img_dir_path, img_name)
                        graph_content.append(Content('image', image_path))
                        with open(image_path, 'wb') as f:
                            f.write(part.blob)
                    except BaseException as e:
                        continue
            self.content_list.append(graph_content)

    def analyse_content(self):
        result = list()
        for graph_content in self.content_list:
            # skip empty graph
            if not graph_content:
                continue
            tmp_list = list()
            for index, run_content in enumerate(graph_content):
                if index == 0:
                    tmp_list.append(run_content)
                else:
                    if run_content.type == graph_content[index-1].type and run_content.type != 'image':
                        tmp_list[-1] = Content(run_content.type, tmp_list[-1].value+run_content.value)
                    else:
                        tmp_list.append(run_content)
            result.append(tmp_list)
        self.content_list = result

    def question_type_analyse(self, content_list):
        qt_pattern = re.compile(r'^([一二三四五六七八九十]{1,3})、\s*(.*?)题.*', re.U)
        for run_content in content_list:
            if run_content.type == 'text':
                try:
                    match_obj = qt_pattern.search(run_content.value)
                    if match_obj:
                        qt_index = match_obj.group(1)
                        qt = match_obj.group(2)
                        if qt in ('选择', '单选'):
                            qt = QuestionType.CHOICE_QUESTION
                        elif qt == '多选':
                            qt = QuestionType.MULTI_CHOICE_QUESTION
                        elif qt == '填空':
                            qt = QuestionType.GAP_FILLING
                        elif qt == '解答':
                            qt = QuestionType.FREE_RESPONSE_QUESTION
                        return qt
                    else:
                        return None
                except:
                    traceback.print_exc()
                    return None

    def word_analyse(self):
        """
        Analyse the entire word document
        :return:
        """
        word_analyse_status = WAStatus.INIT
        paragraph_count = len(self.content_list)
        index = 0
        # question type index
        qt_index = 0
        # 
        question_list = list()

        analysing_question = Question()

        def question_analyse(content_list, question_type):
            """

            :param content_list:
            :param question_type:
            :return:
            """
            nonlocal word_analyse_status
            nonlocal analysing_question
            qt_pattern = re.compile(r'^([一二三四五六七八九十]{1,3})、\s*(.*?)题.*', re.U)
            if question_type in (QuestionType.CHOICE_QUESTION, QuestionType.MULTI_CHOICE_QUESTION):
                start_pattern = re.compile(r'^([\d]{1,3})[、\.]\s*.+', re.U)
                answer_item_pattern = re.compile(r'[ABCDEFGHIJK][\s\.、].+')
            elif question_type == QuestionType.GAP_FILLING:
                start_pattern = re.compile(r'^([\d]{1,3})[、\.][\s\t]*(.+)', re.U)
            elif question_type == QuestionType.FREE_RESPONSE_QUESTION:
                start_pattern = re.compile(r'^([\d]{1,3})[、\.][\s\t]*(.+)', re.U)
                sub_question_pattern = re.compile(r'^\(\d\).*')

            for run_content in content_list:
                if word_analyse_status == WAStatus.QUESTION_TYPE_ANALYSE:
                    # find the start of a question
                    if run_content.type != 'text':
                        continue
                    match_obj = start_pattern.search(run_content.value)
                    if match_obj:
                        analysing_question.common_description.append(run_content)
                        analysing_question.type = question_type
                        word_analyse_status = WAStatus.QUESTION_ANALYSE
                elif word_analyse_status == WAStatus.QUESTION_ANALYSE:
                    if run_content.type == 'text':
                        match_obj = start_pattern.search(run_content.value)
                        if match_obj:
                            word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                            break
                        match_obj = qt_pattern.search(run_content.value)
                        if match_obj:
                            word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                            break
                    if question_type in (QuestionType.CHOICE_QUESTION, QuestionType.MULTI_CHOICE_QUESTION):
                        if run_content.type == 'text':
                            match_obj = answer_item_pattern.search(run_content.value)
                            if match_obj:
                                word_analyse_status = WAStatus.ANSWER_ITEM_ANALYSE
                                analysing_question.answer_items.append(run_content)
                                continue
                        analysing_question.common_description.append(run_content)
                    elif question_type == QuestionType.GAP_FILLING:
                        analysing_question.common_description.append(run_content)
                    elif question_type == QuestionType.FREE_RESPONSE_QUESTION:
                        if run_content.type == 'text':
                            match_obj = sub_question_pattern.search(run_content.value)
                            if match_obj:
                                word_analyse_status = WAStatus.SUB_QUESTION_ANALYSE
                                analysing_question.sub_questions.append(run_content)
                                continue
                        analysing_question.common_description.append(run_content)
                elif word_analyse_status == WAStatus.SUB_QUESTION_ANALYSE:
                    if run_content.type == 'text':
                        match_obj = start_pattern.search(run_content.value)
                        if match_obj:
                            word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                            break
                        match_obj = qt_pattern.search(run_content.value)
                        if match_obj:
                            word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                            break
                        match_obj = sub_question_pattern.search(run_content.value)
                        if match_obj:
                            # fixed: a new answer item found
                            pass
                        analysing_question.sub_questions.append(run_content)
                    else:
                        analysing_question.sub_questions.append(run_content)
                elif word_analyse_status == WAStatus.ANSWER_ITEM_ANALYSE:
                    if run_content.type == 'text':
                        match_obj = start_pattern.search(run_content.value)
                        if match_obj:
                            word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                            break
                        match_obj = qt_pattern.search(run_content.value)
                        if match_obj:
                            word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                            break
                        match_obj = answer_item_pattern.search(run_content.value)
                        if match_obj:
                            # fixed: a new answer item found
                            pass
                        analysing_question.answer_items.append(run_content)
                    else:
                        analysing_question.answer_items.append(run_content)

        qt = None
        while index < paragraph_count:
            paragraph = self.content_list[index]
            if word_analyse_status == WAStatus.INIT:
                qt = self.question_type_analyse(paragraph)
                index += 1
                if qt is not None:
                    word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                continue
            elif word_analyse_status == WAStatus.QUESTION_TYPE_ANALYSE:
                if analysing_question.common_description:
                    question_list.append(analysing_question)
                    analysing_question = Question()
                question_analyse(paragraph, qt)
                index += 1
            elif word_analyse_status == WAStatus.QUESTION_ANALYSE:
                question_analyse(paragraph, qt)
                if word_analyse_status == WAStatus.QUESTION_TYPE_ANALYSE:
                    if analysing_question.common_description:
                        question_list.append(analysing_question)
                    analysing_question = Question()
                    tmp_qt = self.question_type_analyse(paragraph)
                    if tmp_qt is not None:
                        qt = tmp_qt
                        word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                        index += 1
                        continue

                else:
                    index += 1
                    continue
            elif word_analyse_status == WAStatus.ANSWER_ITEM_ANALYSE:
                question_analyse(paragraph, qt)
                if word_analyse_status != WAStatus.ANSWER_ITEM_ANALYSE:
                    if analysing_question.common_description:
                        question_list.append(analysing_question)
                    analysing_question = Question()
                    tmp_qt = self.question_type_analyse(paragraph)
                    if tmp_qt is not None:
                        qt = tmp_qt
                        word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                        index += 1
                        continue
                else:
                    index += 1
                    continue
            elif word_analyse_status == WAStatus.SUB_QUESTION_ANALYSE:
                question_analyse(paragraph, qt)
                if word_analyse_status != WAStatus.SUB_QUESTION_ANALYSE:
                    if analysing_question.common_description:
                        question_list.append(analysing_question)
                    analysing_question = Question()
                    tmp_qt = self.question_type_analyse(paragraph)
                    if tmp_qt is not None:
                        qt = tmp_qt
                        word_analyse_status = WAStatus.QUESTION_TYPE_ANALYSE
                        index += 1
                        continue
                else:
                    index += 1
                    continue

        return question_list

                



if __name__ == "__main__":
    file_path = 'E:/temp/shuxue2.docx'
    x = WorkOperation(file_path)
    x.get_content()
    x.analyse_content()
    m = x.word_analyse()
    for i in m:
        print(i)
